/*
 * File     : CallingConvention.java
 * Created  : 1 May 2011
 *
 * Copyright © 2011 Matthew Wilson (mj. {my-surname} .uk {at} gmail.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.googlecode.dni.library;

import com.googlecode.dni.internal.DniInternal;


/**
 * <p>
 *  Represents the calling conventions for native function calls.
 * </p>
 * <p>
 *  Calling conventions can also modify the name of the function (termed
 *  "mangling").
 * </p>
 * <p>
 *  There are two types of calling convention defined here: specific, and
 *  generic.  The generic types are {@link #STDCALL}, {@link #CDECL}, and
 *  {@link #PLATFORM_DEFAULT}.  All generic types are supported, and will map to
 *  some specific type.  However, note that not all platforms support all
 *  specific types; hence, using {@link #STDCALL} on Windows x64 would actually
 *  map to {@link #X64_WINDOWS}.
 * </p>
 *
 * @author Matthew Wilson
 */
public enum CallingConvention
{

    /** CDecl, also known as Sys-V. */
    X86_CDECL_UNIX,

    /** CDecl on Windows. */
    X86_CDECL_WINDOWS,

    /** Standard C call. */
    X86_STDCALL,

    // TODO v2.0 X86_STDCALL_UNIX?

    /** Microsoft Windows x64 call. */
    X64_WINDOWS,

    /** Sys-V x86_64 call. */
    X86_64_UNIX,

    /**
     * Use the CDecl variant for the current platform.  On Windows x86, this
     * will be {@link #X86_CDECL_WINDOWS}.  On Linux x86, this will be
     * {@link #X86_CDECL_UNIX}.  Otherwise, it will be default calling
     * convention for that platform.
     */
    CDECL( true ),

    /**
     * Use the stdcall variant for the current platform.  On Windows x86, this
     * will be {@link #X86_STDCALL}.  Otherwise, it will be default calling
     * convention for that platform.
     */
    STDCALL( true ),

    /** Use the platform's default conventions. */
    PLATFORM_DEFAULT( true );


    private final boolean generic;


    private CallingConvention()
    {
        this.generic = false;
    }

    private CallingConvention( final boolean generic )
    {
        this.generic = generic;
    }

    /**
     * <p>
     *  Determines the default conventions for the platform.  This value is used
     *  where {@link #PLATFORM_DEFAULT} is specified.
     * </p>
     *
     * @return the default conventions; guaranteed not to be
     *         {@link #PLATFORM_DEFAULT}
     */
    public static CallingConvention getDefault()
    {
        return DniInternal.PLATFORM.getActualCallingConvention( PLATFORM_DEFAULT );
    }

    /**
     * <p>
     *  Determines the actual convention used for this defined type.
     * </p>
     * <p>
     *  The types {@link #PLATFORM_DEFAULT}, {@link #CDECL}, and
     *  {@link #STDCALL} are generic types that must be supported on all
     *  platforms.  This method call determines what the actual convention is.
     * </p>
     *
     * @return the actual convention; guaranteed not to be
     *         {@linkplain #isGeneric() generic}
     *
     * @throws UnsupportedOperationException
     *             if the underlying convention is not supported
     */
    public CallingConvention getActual()
    {
        if ( !this.generic )
        {
            return this;
        }

        CallingConvention actual = DniInternal.PLATFORM.getActualCallingConvention( this );
        assert actual != null : this;
        assert !actual.generic : this;

        return actual;
    }

    /**
     * <p>
     *  Determines whether this calling convention is a generic type.
     * </p>
     * <p>
     *  All generic types are guaranteed to be supported.  However, generic
     *  types may be supported as a different type; for example, on Windows x64,
     *  {@link #STDCALL} and {@link #CDECL} are always just
     *  {@link #X64_WINDOWS}.
     * </p>
     *
     * @return the generic
     */
    public boolean isGeneric()
    {
        return this.generic;
    }

    /**
     * <p>
     *  Determines whether this calling convention is supported on this
     *  platform.
     * </p>
     *
     * @return <code>true</code> iff supported
     */
    public boolean isSupported()
    {
        if ( this.generic )
        {
            return true;
        }

        return DniInternal.PLATFORM.getSupportedCallingConventions().contains( this );
    }

}
